package com.fsserver.common.exception;

import com.fsserver.common.api.ApiResponse;
import com.fsserver.common.utils.MDCTraceUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Set;
import java.util.concurrent.TimeoutException;

/**
 * 全局异常处理器
 * Global Exception Handler
 *
 * @author fsserver
 * @date 2023-03-13
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理业务异常
     * Handle business exception
     *
     * @param e 业务异常
     * @return API响应
     */
    @ExceptionHandler(BusinessException.class)
    public ApiResponse<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        String businessId = MDCTraceUtils.getBusinessId();
        
        log.warn("业务异常 [traceId: {}, businessId: {}, uri: {}]: {}", 
                traceId, businessId, requestURI, e.getMessage());
        
        return ApiResponse.error(e.getCode(), e.getMessage());
    }

    /**
     * 处理参数校验异常
     * Handle validation exception
     *
     * @param e 参数校验异常
     * @return API响应
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        BindingResult bindingResult = e.getBindingResult();
        StringBuilder sb = new StringBuilder();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            sb.append(fieldError.getField()).append(": ").append(fieldError.getDefaultMessage()).append(", ");
        }
        String message = sb.toString();
        if (message.length() > 2) {
            message = message.substring(0, message.length() - 2);
        }
        
        log.warn("参数校验异常 [traceId: {}, uri: {}]: {}", traceId, requestURI, message);
        
        return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), message);
    }

    /**
     * 处理绑定异常
     * Handle bind exception
     *
     * @param e 绑定异常
     * @return API响应
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Void> handleBindException(BindException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        BindingResult bindingResult = e.getBindingResult();
        StringBuilder sb = new StringBuilder();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            sb.append(fieldError.getField()).append(": ").append(fieldError.getDefaultMessage()).append(", ");
        }
        String message = sb.toString();
        if (message.length() > 2) {
            message = message.substring(0, message.length() - 2);
        }
        
        log.warn("绑定异常 [traceId: {}, uri: {}]: {}", traceId, requestURI, message);
        
        return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), message);
    }

    /**
     * 处理约束违反异常
     * Handle constraint violation exception
     *
     * @param e 约束违反异常
     * @return API响应
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Void> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        StringBuilder sb = new StringBuilder();
        for (ConstraintViolation<?> violation : violations) {
            sb.append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append(", ");
        }
        String message = sb.toString();
        if (message.length() > 2) {
            message = message.substring(0, message.length() - 2);
        }
        
        log.warn("约束违反异常 [traceId: {}, uri: {}]: {}", traceId, requestURI, message);
        
        return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), message);
    }
    
    /**
     * 处理请求方法不支持异常
     * Handle request method not supported exception
     *
     * @param e 请求方法不支持异常
     * @return API响应
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public ApiResponse<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        String method = request.getMethod();
        
        log.warn("请求方法不支持 [traceId: {}, uri: {}, method: {}]: {}", 
                traceId, requestURI, method, e.getMessage());
        
        return ApiResponse.error(HttpStatus.METHOD_NOT_ALLOWED.value(), 
                String.format("不支持%s请求方法", method));
    }
    
    /**
     * 处理请求参数缺失异常
     * Handle missing servlet request parameter exception
     *
     * @param e 请求参数缺失异常
     * @return API响应
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        log.warn("请求参数缺失 [traceId: {}, uri: {}]: {}", traceId, requestURI, e.getMessage());
        
        return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), 
                String.format("缺少必要的请求参数: %s", e.getParameterName()));
    }
    
    /**
     * 处理请求体不可读异常
     * Handle http message not readable exception
     *
     * @param e 请求体不可读异常
     * @return API响应
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        log.warn("请求体不可读 [traceId: {}, uri: {}]: {}", traceId, requestURI, e.getMessage());
        
        return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), "请求体格式错误或内容不可读");
    }
    
    /**
     * 处理404异常
     * Handle no handler found exception
     *
     * @param e 404异常
     * @return API响应
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiResponse<Void> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        log.warn("资源不存在 [traceId: {}, uri: {}]: {}", traceId, requestURI, e.getMessage());
        
        return ApiResponse.error(HttpStatus.NOT_FOUND.value(), 
                String.format("请求的资源不存在: %s", requestURI));
    }
    
    /**
     * 处理SQL异常
     * Handle SQL exception
     *
     * @param e SQL异常
     * @return API响应
     */
    @ExceptionHandler(SQLException.class)
    public ApiResponse<Void> handleSQLException(SQLException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        log.error("SQL异常 [traceId: {}, uri: {}]: {}", traceId, requestURI, e.getMessage(), e);
        
        return ApiResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "数据库操作异常");
    }
    
    /**
     * 处理IO异常
     * Handle IO exception
     *
     * @param e IO异常
     * @return API响应
     */
    @ExceptionHandler(IOException.class)
    public ApiResponse<Void> handleIOException(IOException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        log.error("IO异常 [traceId: {}, uri: {}]: {}", traceId, requestURI, e.getMessage(), e);
        
        return ApiResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "IO操作异常");
    }
    
    /**
     * 处理超时异常
     * Handle timeout exception
     *
     * @param e 超时异常
     * @return API响应
     */
    @ExceptionHandler({TimeoutException.class, AsyncRequestTimeoutException.class})
    @ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
    public ApiResponse<Void> handleTimeoutException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        
        log.error("请求超时 [traceId: {}, uri: {}]: {}", traceId, requestURI, e.getMessage(), e);
        
        return ApiResponse.error(HttpStatus.REQUEST_TIMEOUT.value(), "请求处理超时，请稍后重试");
    }

    /**
     * 处理其他异常
     * Handle other exceptions
     *
     * @param e 异常
     * @return API响应
     */
    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String traceId = MDCTraceUtils.getTraceId();
        String method = request.getMethod();
        
        log.error("系统异常 [traceId: {}, uri: {}, method: {}]: {}", 
                traceId, requestURI, method, e.getMessage(), e);
        
        return ApiResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "系统错误，请稍后再试");
    }
} 